return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
}
+ public function buildSubstring( $input, $startPosition, $length = null ) {
+ $this->assertBuildSubstringParams( $startPosition, $length );
+ $params = [ $input, $startPosition ];
+ if ( $length !== null ) {
+ $params[] = $length;
+ }
+ return 'SUBSTR(' . implode( ',', $params ) . ')';
+ }
+
/**
* @param string $field Field or column to cast
* @return string
return $this->__call( __FUNCTION__, func_get_args() );
}
+ public function buildSubstring( $input, $startPosition, $length = null ) {
+ return $this->__call( __FUNCTION__, func_get_args() );
+ }
+
public function buildStringCast( $field ) {
return $this->__call( __FUNCTION__, func_get_args() );
}
return '(' . $this->selectSQLText( $table, $fld, $conds, null, [], $join_conds ) . ')';
}
+ public function buildSubstring( $input, $startPosition, $length = null ) {
+ $this->assertBuildSubstringParams( $startPosition, $length );
+ $functionBody = "$input FROM $startPosition";
+ if ( $length !== null ) {
+ $functionBody .= " FOR $length";
+ }
+ return 'SUBSTRING(' . $functionBody . ')';
+ }
+
+ /**
+ * Check type and bounds for parameters to self::buildSubstring()
+ *
+ * All supported databases have substring functions that behave the same for
+ * positive $startPosition and non-negative $length, but behaviors differ when
+ * given 0 or negative $startPosition or negative $length. The simplest
+ * solution to that is to just forbid those values.
+ *
+ * @param int $startPosition
+ * @param int|null $length
+ * @since 1.31
+ */
+ protected function assertBuildSubstringParams( $startPosition, $length ) {
+ if ( !is_int( $startPosition ) || $startPosition <= 0 ) {
+ throw new InvalidArgumentException(
+ '$startPosition must be a positive integer'
+ );
+ }
+ if ( !( is_int( $length ) && $length >= 0 || $length === null ) ) {
+ throw new InvalidArgumentException(
+ '$length must be null or an integer greater than or equal to 0'
+ );
+ }
+ }
+
public function buildStringCast( $field ) {
return $field;
}
return $sql;
}
+ public function buildSubstring( $input, $startPosition, $length = null ) {
+ $this->assertBuildSubstringParams( $startPosition, $length );
+ if ( $length === null ) {
+ /**
+ * MSSQL doesn't allow an empty length parameter, so when we don't want to limit the
+ * length returned use the default maximum size of text.
+ * @see https://docs.microsoft.com/en-us/sql/t-sql/statements/set-textsize-transact-sql
+ */
+ $length = 2147483647;
+ }
+ return 'SUBSTRING(' . implode( ',', [ $input, $startPosition, $length ] ) . ')';
+ }
+
/**
* Returns an associative array for fields that are of type varbinary, binary, or image
* $table can be either a raw table name or passed through tableName() first
}
}
+ public function buildSubstring( $input, $startPosition, $length = null ) {
+ $this->assertBuildSubstringParams( $startPosition, $length );
+ $params = [ $input, $startPosition ];
+ if ( $length !== null ) {
+ $params[] = $length;
+ }
+ return 'SUBSTR(' . implode( ',', $params ) . ')';
+ }
+
/**
* @param string $field Field or column to cast
* @return string
*/
namespace Wikimedia\Rdbms;
+use InvalidArgumentException;
use Wikimedia\ScopedCallback;
use RuntimeException;
use UnexpectedValueException;
$delim, $table, $field, $conds = '', $join_conds = []
);
+ /**
+ * Build a SUBSTRING function.
+ *
+ * Behavior for non-ASCII values is undefined.
+ *
+ * @param string $input Field name
+ * @param int $startPosition Positive integer
+ * @param int|null $length Non-negative integer length or null for no limit
+ * @throws InvalidArgumentException
+ * @return string SQL text
+ * @since 1.31
+ */
+ public function buildSubString( $input, $startPosition, $length = null );
+
/**
* @param string $field Field or column to cast
* @return string
--- /dev/null
+<?php
+
+class DatabaseOracleTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseOracle
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseOracle::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ];
+ }
+
+ /**
+ * @covers DatabaseOracle::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $mockDb = $this->getMockDb();
+ $output = $mockDb->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers DatabaseOracle::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $mockDb = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $mockDb->buildSubstring( 'foo', $start, $length );
+ }
+
+}
use Wikimedia\Rdbms\TransactionProfiler;
use Wikimedia\Rdbms\DatabaseDomain;
+use Wikimedia\Rdbms\Database;
/**
* Helper for testing the methods from the Database class
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\DatabaseMssql;
+
+class DatabaseMssqlTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseMssql
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseMssql::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTRING(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTRING(someField,1,2147483647)' ];
+ yield [ 'someField', 1, 3333333333, 'SUBSTRING(someField,1,3333333333)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $mockDb = $this->getMockDb();
+ $output = $mockDb->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseMssql::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $mockDb = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $mockDb->buildSubstring( 'foo', $start, $length );
+ }
+
+}
<?php
+use InvalidArgumentException;
use Wikimedia\Rdbms\LikeMatch;
/**
$this->assertFalse( $this->database->tableExists( "tmp_table_2", __METHOD__ ) );
$this->assertFalse( $this->database->tableExists( "tmp_table_3", __METHOD__ ) );
}
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTRING(someField FROM 1 FOR 2)' ];
+ yield [ 'someField', 1, null, 'SUBSTRING(someField FROM 1)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $output = $this->database->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\Database::buildSubstring
+ * @covers Wikimedia\Rdbms\Database::assertBuildSubstringParams
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $this->setExpectedException( InvalidArgumentException::class );
+ $this->database->buildSubstring( 'foo', $start, $length );
+ }
+
}
--- /dev/null
+<?php
+
+use Wikimedia\Rdbms\DatabaseSqlite;
+
+/**
+ * DatabaseSqliteTest is already defined in mediawiki core hence the 'Rdbms' included in this
+ * class name.
+ * The test in core should have mediawiki specific stuff removed and the tests moved to this
+ * rdbms libs test.
+ */
+class DatabaseSqliteRdbmsTest extends PHPUnit\Framework\TestCase {
+
+ use MediaWikiCoversValidator;
+
+ /**
+ * @return PHPUnit_Framework_MockObject_MockObject|DatabaseSqlite
+ */
+ private function getMockDb() {
+ return $this->getMockBuilder( DatabaseSqlite::class )
+ ->disableOriginalConstructor()
+ ->setMethods( null )
+ ->getMock();
+ }
+
+ public function provideBuildSubstring() {
+ yield [ 'someField', 1, 2, 'SUBSTR(someField,1,2)' ];
+ yield [ 'someField', 1, null, 'SUBSTR(someField,1)' ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring
+ * @dataProvider provideBuildSubstring
+ */
+ public function testBuildSubstring( $input, $start, $length, $expected ) {
+ $dbMock = $this->getMockDb();
+ $output = $dbMock->buildSubstring( $input, $start, $length );
+ $this->assertSame( $expected, $output );
+ }
+
+ public function provideBuildSubstring_invalidParams() {
+ yield [ -1, 1 ];
+ yield [ 1, -1 ];
+ yield [ 1, 'foo' ];
+ yield [ 'foo', 1 ];
+ yield [ null, 1 ];
+ yield [ 0, 1 ];
+ }
+
+ /**
+ * @covers Wikimedia\Rdbms\DatabaseSqlite::buildSubstring
+ * @dataProvider provideBuildSubstring_invalidParams
+ */
+ public function testBuildSubstring_invalidParams( $start, $length ) {
+ $dbMock = $this->getMockDb();
+ $this->setExpectedException( InvalidArgumentException::class );
+ $dbMock->buildSubstring( 'foo', $start, $length );
+ }
+
+}